1 /*
2 * $Header: /home/cvs/jakarta-commons/betwixt/src/java/org/apache/commons/betwixt/io/BeanWriter.java,v 1.9 2002/08/01 03:58:01 jstrachan Exp $
3 * $Revision: 1.9 $
4 * $Date: 2002/08/01 03:58:01 $
5 *
6 * ====================================================================
7 *
8 * The Apache Software License, Version 1.1
9 *
10 * Copyright (c) 1999-2002 The Apache Software Foundation. All rights
11 * reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 *
17 * 1. Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 *
20 * 2. Redistributions in binary form must reproduce the above copyright
21 * notice, this list of conditions and the following disclaimer in
22 * the documentation and/or other materials provided with the
23 * distribution.
24 *
25 * 3. The end-user documentation included with the redistribution, if
26 * any, must include the following acknowlegement:
27 * "This product includes software developed by the
28 * Apache Software Foundation (http://www.apache.org/)."
29 * Alternately, this acknowlegement may appear in the software itself,
30 * if and wherever such third-party acknowlegements normally appear.
31 *
32 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
33 * Foundation" must not be used to endorse or promote products derived
34 * from this software without prior written permission. For written
35 * permission, please contact apache@apache.org.
36 *
37 * 5. Products derived from this software may not be called "Apache"
38 * nor may "Apache" appear in their names without prior written
39 * permission of the Apache Group.
40 *
41 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
42 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
43 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
44 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
45 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
46 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
47 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
48 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
49 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
51 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
52 * SUCH DAMAGE.
53 * ====================================================================
54 *
55 * This software consists of voluntary contributions made by many
56 * individuals on behalf of the Apache Software Foundation. For more
57 * information on the Apache Software Foundation, please see
58 * <http://www.apache.org/>.
59 *
60 * $Id: BeanWriter.java,v 1.9 2002/08/01 03:58:01 jstrachan Exp $
61 */
62 package org.apache.commons.betwixt.io;
63
64 import java.beans.IntrospectionException;
65 import java.io.BufferedWriter;
66 import java.io.IOException;
67 import java.io.OutputStream;
68 import java.io.OutputStreamWriter;
69 import java.io.Writer;
70 import java.util.Iterator;
71 import java.util.HashMap;
72
73 import org.apache.commons.logging.Log;
74 import org.apache.commons.logging.LogFactory;
75
76 import org.apache.commons.betwixt.AttributeDescriptor;
77 import org.apache.commons.betwixt.ElementDescriptor;
78 import org.apache.commons.betwixt.XMLBeanInfo;
79 import org.apache.commons.betwixt.XMLIntrospector;
80 import org.apache.commons.betwixt.expression.Context;
81 import org.apache.commons.betwixt.expression.Expression;
82 import org.apache.commons.betwixt.io.id.SequentialIDGenerator;
83
84 import org.xml.sax.SAXException;
85
86 /*** <p><code>BeanWriter</code> outputs beans as XML to an io stream.</p>
87 *
88 * <p>The output for each bean is an xml fragment
89 * (rather than a well-formed xml-document).
90 * This allows bean representations to be appended to a document
91 * by writing each in turn to the stream.
92 * So to create a well formed xml document,
93 * you'll need to write the prolog to the stream first.
94 * If you append more than one bean to the stream,
95 * then you'll need to add a wrapping root element as well.
96 *
97 * <p> The line ending to be used is set by {@link #setEndOfLine}.
98 *
99 * <p> The output can be formatted (with whitespace) for easy reading
100 * by calling {@link #enablePrettyPrint}.
101 * The output will be indented.
102 * The indent string used is set by {@link #setIndent}.
103 *
104 * <p> Bean graphs can sometimes contain cycles.
105 * Care must be taken when serializing cyclic bean graphs
106 * since this can lead to infinite recursion.
107 * The approach taken by <code>BeanWriter</code> is to automatically
108 * assign an <code>ID</code> attribute value to beans.
109 * When a cycle is encountered,
110 * an element is written that has the <code>IDREF</code> attribute set to the
111 * id assigned earlier.
112 *
113 * <p> The names of the <code>ID</code> and <code>IDREF</code> attributes used
114 * can be customized by the <code>XMLBeanInfo</code>.
115 * The id's used can also be customized by the user
116 * via <code>IDGenerator</code> subclasses.
117 * The implementation used can be set by the <code>IdGenerator</code> property.
118 * BeanWriter defaults to using <code>SequentialIDGenerator</code>
119 * which supplies id values in numeric sequence.
120 *
121 * <p>If generated <code>ID</code> attribute values are not acceptable in the output,
122 * then this can be disabled by setting the <code>WriteIDs</code> property to false.
123 * If a cyclic reference is encountered in this case then a
124 * <code>CyclicReferenceException</code> will be thrown.
125 * When the <code>WriteIDs</code> property is set to false,
126 * it is recommended that this exception is caught by the caller.
127 *
128 *
129 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
130 * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
131 * @version $Revision: 1.9 $
132 */
133 public class BeanWriter extends AbstractBeanWriter {
134
135 /*** Escaped <code><</code> entity */
136 private final static String LESS_THAN_ENTITY = "<";
137 /*** Escaped <code>></code> entity */
138 private final static String GREATER_THAN_ENTITY = ">";
139 /*** Escaped <code>&</code> entity */
140 private final static String AMPERSAND_ENTITY = "&";
141 /*** Escaped <code>'</code> entity */
142 private final static String APOSTROPHE_ENTITY = "'";
143 /*** Escaped <code>"</code> entity */
144 private final static String QUOTE_ENTITY = """;
145
146 /*** Where the output goes */
147 private Writer writer;
148 /*** text used for end of lines. Defaults to <code>\n</code>*/
149 private static final String EOL = "\n";
150 /*** text used for end of lines. Defaults to <code>\n</code>*/
151 private String endOfLine = EOL;
152 /*** indentation text */
153 private String indent;
154
155 /*** should we flush after writing bean */
156 private boolean autoFlush;
157 /*** Log used for logging (Doh!) */
158 private Log log = LogFactory.getLog( BeanWriter.class );
159
160 /***
161 * <p> Constructor uses <code>System.out</code> for output.</p>
162 */
163 public BeanWriter() {
164 this( System.out );
165 }
166
167 /***
168 * <p> Constuctor uses given <code>OutputStream</code> for output.</p>
169 *
170 * @param out write out representations to this stream
171 */
172 public BeanWriter(OutputStream out) {
173 this.writer = new BufferedWriter( new OutputStreamWriter( out ) );
174 this.autoFlush = true;
175 }
176
177 /***
178 * <p> Constructor sets writer used for output.</p>
179 *
180 * @param writer write out representations to this writer
181 */
182 public BeanWriter(Writer writer) {
183 this.writer = writer;
184 }
185
186 /***
187 * A helper method that allows you to write the XML Declaration.
188 * This should only be called once before you output any beans.
189 *
190 * @param xmlDeclaration is the XML declaration string typically of
191 * the form "<xml version='1.0' encoding='UTF-8' ?>
192 */
193 public void writeXmlDeclaration(String xmlDeclaration) throws IOException {
194 writer.write( xmlDeclaration );
195 writePrintln();
196 }
197
198 /***
199 * Allows output to be flushed on the underlying output stream
200 */
201 public void flush() throws IOException {
202 writer.flush();
203 }
204
205 /***
206 * Closes the underlying output stream
207 */
208 public void close() throws IOException {
209 writer.close();
210 }
211
212 public void write(Object bean) throws IOException, SAXException, IntrospectionException {
213
214 super.write(bean);
215
216 if ( autoFlush ) {
217 writer.flush();
218 }
219 }
220
221
222 /***
223 * <p> Switch on formatted output.
224 * This sets the end of line and the indent.
225 * The default is adding 2 spaces and a newline
226 */
227 public void enablePrettyPrint() {
228 endOfLine = EOL;
229 indent = " ";
230 }
231
232 /*** Returns the string used for end of lines */
233 public String getEndOfLine() {
234 return endOfLine;
235 }
236
237 /***
238 * Sets the string used for end of lines
239 * Produces a warning the specified value contains an invalid whitespace character
240 */
241 public void setEndOfLine(String endOfLine) {
242 this.endOfLine = endOfLine;
243 for (int i = 0; i < endOfLine.length(); i++) {
244 if (!Character.isWhitespace(endOfLine.charAt(i))) {
245 log.warn("Invalid EndOfLine character(s)");
246 break;
247 }
248 }
249
250 }
251
252 /*** Returns the string used for indentation */
253 public String getIndent() {
254 return indent;
255 }
256
257 /*** Sets the string used for end of lines */
258 public void setIndent(String indent) {
259 this.indent = indent;
260 }
261
262 /***
263 * <p> Get the current level for logging. </p>
264 *
265 * @return a <code>org.apache.commons.logging.Log</code> level constant
266 */
267 public Log getLog() {
268 return log;
269 }
270
271 /***
272 * <p> Set the current logging level. </p>
273 *
274 * @param level a <code>org.apache.commons.logging.Log</code> level constant
275 */
276 public void setLog(Log log) {
277 this.log = log;
278 }
279
280
281 // Expression methods
282 //-------------------------------------------------------------------------
283
284 /*** Express an element tag start using given qualified name */
285 protected void expressElementStart(String qualifiedName) throws IOException {
286 if ( qualifiedName == null ) {
287 // XXX this indicates a programming error
288 log.fatal( "[expressElementStart]Qualified name is null." );
289 throw new RuntimeException( "Qualified name is null." );
290 }
291
292 writePrintln();
293 writeIndent();
294 writer.write( '<' );
295 writer.write( qualifiedName );
296 }
297
298 protected void expressTagClose() throws IOException {
299 writer.write( '>' );
300 }
301
302 /*** Express an element end tag using given qualifiedName */
303 protected void expressElementEnd(String qualifiedName) throws IOException {
304 if (qualifiedName == null) {
305 // XXX this indicates a programming error
306 log.fatal( "[expressElementEnd]Qualified name is null." );
307 throw new RuntimeException( "Qualified name is null." );
308 }
309
310 writer.write( "</" );
311 writer.write( qualifiedName );
312 writer.write( '>' );
313 }
314
315 /*** Express an empty element end */
316 protected void expressElementEnd() throws IOException {
317 writer.write( "/>" );
318 }
319
320 /*** Express body text */
321 protected void expressBodyText(String text) throws IOException {
322 if ( text == null ) {
323 // XXX This is probably a programming error
324 log.error( "[expressBodyText]Body text is null" );
325
326 } else {
327 writer.write( escapeBodyValue(text) );
328 }
329 }
330
331 /*** Express an attribute */
332 protected void expressAttribute(
333 String qualifiedName,
334 String value)
335 throws
336 IOException{
337 if ( value == null ) {
338 // XXX probably a programming error
339 log.error( "Null attribute value." );
340 return;
341 }
342
343 if ( qualifiedName == null ) {
344 // XXX probably a programming error
345 log.error( "Null attribute value." );
346 return;
347 }
348
349 writer.write( ' ' );
350 writer.write( qualifiedName );
351 writer.write( "=\"" );
352 writer.write( escapeAttributeValue(value) );
353 writer.write( '\"' );
354 }
355
356
357 // Implementation methods
358 //-------------------------------------------------------------------------
359
360 /*** Writes out an empty line.
361 * Uses current <code>endOfLine</code>.
362 */
363 protected void writePrintln() throws IOException {
364 if ( endOfLine != null ) {
365 writer.write( endOfLine );
366 }
367 }
368
369 /*** Writes out <code>indent</code>'s to the current <code>indentLevel</code>
370 */
371 protected void writeIndent() throws IOException {
372 if ( indent != null ) {
373 for ( int i = 0; i < indentLevel; i++ ) {
374 writer.write( getIndent() );
375 }
376 }
377 }
378
379 /***
380 * <p>Escape the <code>toString</code> of the given object.
381 * For use as body text.</p>
382 */
383 protected String escapeBodyValue(Object value) {
384 StringBuffer buffer = new StringBuffer(value.toString());
385 for (int i=0, size = buffer.length(); i <size; i++) {
386 switch (buffer.charAt(i)) {
387 case '<':
388 buffer.replace(i, i+1, LESS_THAN_ENTITY);
389 size += 3;
390 i+=3;
391 break;
392 case '>':
393 buffer.replace(i, i+1, GREATER_THAN_ENTITY);
394 size += 3;
395 i += 3;
396 break;
397 case '&':
398 buffer.replace(i, i+1, AMPERSAND_ENTITY);
399 size += 4;
400 i += 4;
401 break;
402 }
403 }
404 return buffer.toString();
405 }
406
407 /***
408 * <p>Escape the <code>toString</code> of the given object.
409 * For use in an attribute value.</p>
410 */
411 protected String escapeAttributeValue(Object value) {
412 StringBuffer buffer = new StringBuffer(value.toString());
413 for (int i=0, size = buffer.length(); i <size; i++) {
414 switch (buffer.charAt(i)) {
415 case '<':
416 buffer.replace(i, i+1, LESS_THAN_ENTITY);
417 size += 3;
418 i+=3;
419 break;
420 case '>':
421 buffer.replace(i, i+1, GREATER_THAN_ENTITY);
422 size += 3;
423 i += 3;
424 break;
425 case '&':
426 buffer.replace(i, i+1, AMPERSAND_ENTITY);
427 size += 4;
428 i += 4;
429 break;
430 case '\'':
431 buffer.replace(i, i+1, APOSTROPHE_ENTITY);
432 size += 4;
433 i += 4;
434 break;
435 case '\"':
436 buffer.replace(i, i+1, QUOTE_ENTITY);
437 size += 5;
438 i += 5;
439 break;
440 }
441 }
442 return buffer.toString();
443 }
444
445 }
This page was automatically generated by Maven